class_name Level
extends Node2D


# ui
const UI_QUICK_SWITCH_ITEM = preload("res://ui/quick_switch_item/ui_quick_switch_item.tscn")
var current_quick_switch_item_ui: Control = null
@onready var canvas_layer: CanvasLayer = $CanvasLayer
@onready var ui_black_fade: UIBlackFade = $CanvasLayer/UIBlackFade

# ui card
const UI_CARD = preload("res://ui/ui_card/ui_card.tscn")
var ui_card_view: Control = null

# particles
@onready var particles_parent: Node2D = $Particles

var player: Player = null

# pickup item tips
@onready var pickup_item_tips_parent: Node2D = $PickupItemTips

# level data
var level_data: LevelData = null

# player
@onready var player_parent: Node2D = $PlayerParent

# camera
@onready var camera_parent: Node2D = $CameraParent
var camera: SimpleCamera = null

# unit parent
@onready var units_parent: Node2D = $UnitsParent

# damage number
@onready var damage_numbers_parent: Node2D = $DamageNumbers

# drop
@onready var drop_items_parent: Node2D = $DropItems
@export var drop_x_rand_range: float = 10
@export var drop_item_interval: float = 0.1

# bullet
@onready var bullets_parent: Node2D = $BulletsParent

# crosshair
@onready var cross_hair: Node2D = $CrossHair/CrossHair

# ui player info
@onready var ui_player_info: Control = $CanvasLayer/UIPlayerInfo

# nagigation
@onready var navigation_region_2d: NavigationRegion2D = $NavigationRegion2D

# regions
@onready var regions_parent: Node2D = $RegionsParent
@export var regions: Array[LevelRegion] = []
@export var region_width: float = 1600
@export var region_height: float = 1600

# game map
@onready var game_map: GameMap = $GameMap/GameMap
var game_map_target_a: float = 0.0
var game_map_max_scale: float = 2.0
var game_map_min_scale: float = 0.4
var game_map_scale_step: float = 0.2
var game_map_target_scale: float = 1.0

# bullet info ui
@onready var ui_bullet_remain_info: Control = $CanvasLayer/UIBulletRemainInfo

# fisheye transition
@onready var fisheye_transition: FisheyeTransition = $CanvasLayer/FisheyeTransition
@onready var white_fade_effect: Control = $CanvasLayer/WhiteFadeEffect

# active process logic
var update_process_active_timer: float = 0.0
@export var update_process_active_interval: float = 1.0
var is_processing_active: bool = false

# all crates
var all_crates: Array = []

# all doors:
var all_doors: Array = []

# all waters:
var all_waters: Array = []
var all_water_rects: Array = []

# all damage areas
var all_damage_areas: Array = []

# all dark spaces
var all_dark_spaces: Array = []

# all traps:
var all_traps: Array = []

# default drop config
@export var default_drop_config: DropConfig

# all random points
var all_random_points: Array = []

# game pause menu
@onready var ui_game_pause_menu: UIGamePauseMenu = $CanvasLayer/UIGamePauseMenu

# operation instruction for level
@onready var ui_operation_instruction_for_level: UIOperationInstructionForLevel = $CanvasLayer/UIOperationInstructionForLevel


func init_regions():
	for index in range(regions_parent.get_child_count()):
		regions.append(regions_parent.get_child(index)) 
	
	regions.shuffle()
	
	var index = 0
	for region in regions:
		region.global_position = Vector2(region_width * (index % 3), region_height * (index / 3))
		index += 1
		region.region_data.position = region.global_position


func _ready() -> void:
	ui_game_pause_menu.level = self
	
	randomize()
	
	update_process_active_timer = update_process_active_interval
	
	ui_player_info.visible = true
	navigation_region_2d.visible = true
	
	init_regions()
	
	GlobalSignals.player_info_changed.emit()
	Input.mouse_mode = Input.MOUSE_MODE_HIDDEN
	show_crosshair()
	
	init_all_random_points()
	
	
func init_all_random_points():
	all_random_points = get_all_random_points()
	for random_point in all_random_points:
		random_point.visible = false


func open_quick_switch_item_by_type(types: Array[ItemBaseConfig.ItemType]):
	destroy_current_quick_switch_item_ui()
	
	var items_data = GlobalData.get_items_by_type(types)
	if items_data.size() <= 0: return
	
	var inst = UI_QUICK_SWITCH_ITEM.instantiate()
	canvas_layer.add_child(inst)
	inst.level = self
	inst.signal_on_close.connect(on_quick_switch_item_close)
	inst.open(items_data)
	
	current_quick_switch_item_ui = inst
	
	hide_crosshair()
	#camera.is_following = false
	

func on_quick_switch_item_close():
	show_crosshair()
	camera.is_following = true
	

func destroy_current_quick_switch_item_ui():
	if not current_quick_switch_item_ui: return
	
	current_quick_switch_item_ui.close()
	current_quick_switch_item_ui = null


func emit_particle(particle_resource_name: String, point: Vector2):
	var res = ResourceManager.get_resource(particle_resource_name) as PackedScene
	var inst = res.instantiate()
	particles_parent.add_child(inst)
	inst.global_position = point
	return inst
	

func emit_particle_face(particle_resource_name: String, point: Vector2, face: int):
	var res = ResourceManager.get_resource(particle_resource_name) as PackedScene
	var inst = res.instantiate() as ParticleBase
	particles_parent.add_child(inst)
	inst.global_position = point
	inst.set_face(face)
	inst.play()
	

func show_pickup_tip(item_config:ItemBaseConfig, pos: Vector2):
	var existing_rects: Array[Rect2] = []
	for index in range(pickup_item_tips_parent.get_child_count()):
		var child = pickup_item_tips_parent.get_child(index)
		if not child.is_disappearing():
			var rect = child.get_bound_rect()
			existing_rects.append(rect)
	
	var res = ResourceManager.get_resource("pickup_item_tip") as PackedScene
	var inst = res.instantiate()
	pickup_item_tips_parent.add_child(inst)
	inst.init(item_config)
	
	var target_size = (inst.get_bound_rect() as Rect2).size
	var target_pos = pos - Vector2(target_size.x / 2.0, target_size.y)
	var target_rect = Rect2(target_pos, target_size)
	target_rect = place_rect(target_rect, existing_rects, target_size.y + 2)
	
	inst.position = target_rect.position
	inst.start_anim()
	
	
func place_rect(new_rect: Rect2, existing_rects: Array[Rect2], rect_height: float) -> Rect2:
	var candidate = new_rect
	var overlap_found = true

	while overlap_found:
		overlap_found = false
		for r in existing_rects:
			if candidate.intersects(r): # true 表示包含边界重叠
				candidate.position.y -= rect_height
				overlap_found = true
				break   # 发现重叠就立刻重新检测
				
	return candidate


func create_player(data: PlayerData):
	var resource_name = data.config.unit_resource_name
	var player_packed_scene = ResourceManager.get_resource(resource_name)
	var player_inst = player_packed_scene.instantiate()
	player_parent.add_child(player_inst)
	
	if data.pos == Vector2.ZERO:
		var player_born_position = pick_random_player_born_position()
		data.pos = player_born_position
		print("player born position: ", player_born_position)
	
	player = player_inst as Player
	player.level = self
	player.init_by_data(data)
	
	print(player.global_position)
	
	var camera = create_camera()
	camera.follow_target = player
	

func get_all_random_points():
	var all_random_position: Array[Marker2D] = []
	for region in regions:
		all_random_position.append_array(region.get_all_player_born_random_points()) 
	return all_random_position
	

func pick_random_player_born_position():
	var result_marker = all_random_points[randi_range(0, all_random_points.size() - 1)]
	print("player born marker position: ", result_marker.name, result_marker.global_position)
	return result_marker.global_position
	

func create_camera():
	var camera_inst = ResourceManager.get_resource("camera_2d").instantiate()
	camera_parent.add_child(camera_inst)
	camera = camera_inst
	camera.level = self
	
	# camera limit
	camera.limit_left = 0
	camera.limit_top = 0
	camera.limit_right = 4800
	camera.limit_bottom = 4800
	
	return camera_inst
	
	
func init_units():
	for unit_data in level_data.units_data:
		create_unit_by_unit_data(unit_data)
		
	update_born_point_data()
	level_data.unit_born_points_data = get_all_born_point_data()
	
	for region in regions:
		var unit_born_points = region.get_all_born_point()
		for unit_born_point in unit_born_points:
			var unit_born_point_data = unit_born_point.data
			if unit_born_point_data.born_type == UnitBornPointData.BornType.OneShot and not unit_born_point_data.finished:
				born_new_unit_one_shot(unit_born_point)
				

func create_unit_by_unit_data(unit_data: UnitBaseData):
	var resource_name = unit_data.config.unit_resource_name
	var unit_packed_scene = ResourceManager.get_resource(resource_name) as PackedScene
	var unit_inst = unit_packed_scene.instantiate()
	
	unit_inst.level = self
	units_parent.add_child(unit_inst)
	unit_inst.init_by_data(unit_data)
	
	return unit_inst


func born_new_unit_one_shot(unit_born_point: UnitBornPoint):
	unit_born_point.data.finished = true
	
	var unit_data = unit_born_point.data.unit_data.duplicate(true) as UnitBaseData
	unit_data.hp = unit_data.config.base_life
	unit_data.max_hp = unit_data.config.base_life
	unit_data.home_position = unit_born_point.global_position
	level_data.units_data.append(unit_data)
	
	unit_data.pos = unit_born_point.global_position
	create_unit_by_unit_data(unit_data) 


func update_born_point_data():
	for born_point_data in level_data.unit_born_points_data:
		var target_born_point = find_born_point_by_id(born_point_data.id) as UnitBornPoint
		if not target_born_point: continue
		target_born_point.data = born_point_data
		

func get_all_born_point_data() -> Array[UnitBornPointData]:
	var result: Array[UnitBornPointData] = []
	
	for region in regions:
		var region_all_born_point_data = region.get_all_born_point_data()
		result.append_array(region_all_born_point_data)
	
	return result
		

func find_born_point_by_id(id: String):
	for region in regions:
		var result = region.find_born_point_by_id(id)
		if result: return result
	return null
	

func collect_data():
	player.collect_data()
	collect_data_units()
	collect_data_dropitems()


func collect_data_units():
	level_data.units_data = []
	for index in range(units_parent.get_child_count()):
		var child = units_parent.get_child(index) as UnitBase
		level_data.units_data.append(child.unit_data)
		child.collect_data()
		

func raise_damage_number(damage_number: int, pos: Vector2, scale: float = 1.0, color: Color = Color("884b2b")):
	var inst = (ResourceManager.get_resource("ui_damage_number") as PackedScene).instantiate() as UIDamageNumber
	damage_numbers_parent.add_child(inst)
	inst.global_position = pos
	inst.set_damage_number(damage_number, scale, color)
	inst.start_anim()
	

func clear_dead_unit(unit_data: UnitBaseData):
	level_data.units_data.erase(unit_data)
	

func start_drop(drop_config: DropConfig, pos: Vector2):
	if not drop_config:
		drop_config = default_drop_config
	
	var final_drop_items: Array = []
	var rand_counter = 0
	
	for drop_item_config in drop_config.drop_items_config:
		for i in range(drop_item_config.max_count):
			if randf() <= drop_item_config.drop_rate:
				final_drop_items.append(drop_item_config.drop_item_name)
		rand_counter += 1
		if rand_counter >= 20:
			rand_counter = 0
			await get_tree().physics_frame
				
	for final_drop_item in final_drop_items:
		drop_item(final_drop_item, pos + Vector2.RIGHT * randf_range(-drop_x_rand_range/2.0, drop_x_rand_range/2.0))
		await get_tree().create_timer(drop_item_interval).timeout
		
	
func drop_item(drop_item_name: String, pos: Vector2):
	var inst = (ResourceManager.get_resource(drop_item_name) as PackedScene).instantiate() as Node2D
	drop_items_parent.add_child(inst)
	inst.global_position = pos
	inst.start_drop()
	

func drop_item_no_fly(drop_item_name: String, pos: Vector2):
	var inst = (ResourceManager.get_resource(drop_item_name) as PackedScene).instantiate() as Node2D
	drop_items_parent.add_child(inst)
	inst.global_position = pos
	inst.start_drop_no_fly()
	return inst
	

func throw_item(drop_item_name: String, pos: Vector2, direction: int):
	var inst = (ResourceManager.get_resource(drop_item_name) as PackedScene).instantiate() as Node2D
	drop_items_parent.add_child(inst)
	inst.global_position = pos
	inst.throw(direction)
	
	
func collect_data_dropitems():
	level_data.drop_items_data = []
	for index in range(drop_items_parent.get_child_count()):
		var child = drop_items_parent.get_child(index) as DropItem
		
		var drop_item_data = DropItemData.new()
		drop_item_data.item_config = child.item_config
		drop_item_data.drop_pos = child.global_position
		
		level_data.drop_items_data.append(drop_item_data)
	

func init_by_data(data: LevelData):
	level_data = data
	
	init_regions_by_data()
	
	all_waters = get_all_waters()
	all_water_rects = get_all_water_rects()
	all_traps = get_all_traps()
	
	create_player(GlobalData.player_data)
	var player_start_gravity = player.gravity
	player.gravity = 0
	player.velocity = Vector2.ZERO
	
	init_units()
	init_drop_items()	
	init_crates()
	init_doors()
	init_broken_blocks()
	init_torchs()
	init_torch_switches()
	
	# game map
	game_map.level = self
	game_map.init_by_data(level_data.game_map_data)
	
	all_random_points = get_all_random_points()
	
	all_crates = get_all_crates()
	all_doors = get_all_doors()
	all_damage_areas = get_all_damage_areas()
	all_dark_spaces = get_all_dark_spaces()
	disable_all_entity_process()
	
	camera.follow_immediate()
	player.disable_hurt_collision()
	player.enable_input = false
	
	await update_entity_active_process()
	
	AudioManager.play_music("music_main_level_bg")
	await ui_black_fade.fade_out().finished
	player.gravity = player_start_gravity
	player.enable_hurt_collision()
	player.enable_input = true
	
	if GlobalData.is_new_game:
		ui_operation_instruction_for_level.animation_player.play("show")
		AudioManager.play_sound("sfx_paper_page_flip")
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
		get_tree().paused = true
	

func init_regions_by_data():
	for region_data in level_data.regions_data:
		for region in regions:
			if region.region_data.region_id == region_data.region_id:
				region.init_by_data(region_data)
				
	navigation_region_2d.bake_navigation_polygon()
	
	var all_regions_data: Array[RegionData] = []
	for region in regions:
		all_regions_data.append(region.region_data)
		
	level_data.regions_data = all_regions_data
	
	for region in regions:
		region.init_exit_tilemap_layer()
	

func get_all_traps():
	var result: Array = []
	for region in regions:
		result.append_array(region.get_traps()) 
	return result


func get_all_doors() -> Array[LockDoor]:
	var result: Array[LockDoor] = []
	for region in regions:
		result.append_array(region.get_doors()) 
	return result
	

func init_doors():
	var doors_data = level_data.doors_data
	var doors = get_all_doors()
	
	for door_data in doors_data:
		for door in doors:
			if door.door_data.door_id == door_data.door_id:
				door.init_by_data(door_data)
				break
	
	var all_doors_data: Array[DoorData] = []
	for door in doors:
		all_doors_data.append(door.door_data)
		
	level_data.doors_data = all_doors_data
	

func get_all_switches() -> Array[TorchSwitch]:
	var result: Array[TorchSwitch] = []
	for region in regions:
		result.append_array(region.get_switches()) 
	return result


func init_torch_switches():
	var torch_switches_data = level_data.torch_switches_data
	var switches = get_all_switches()
	
	for torch_switch_data in torch_switches_data:
		for switch in switches:
			if switch.data.id == torch_switch_data.id:
				switch.init_by_data(torch_switch_data)
				break
	
	var all_torch_switches_data: Array[TorchSwitchData] = []
	for switch in switches:
		all_torch_switches_data.append(switch.data)
		
	level_data.torch_switches_data = all_torch_switches_data
	

func get_all_broken_blocks() -> Array[BrokenBlock]:
	var result: Array[BrokenBlock] = []
	for region in regions:
		result.append_array(region.get_broken_blocks()) 
	return result	


func init_broken_blocks():
	var broken_blocks_data = level_data.broken_blocks_data
	var broken_blocks = get_all_broken_blocks()
	
	for broken_block_data in broken_blocks_data:
		for broken_block in broken_blocks:
			if broken_block.data.id == broken_block_data.id:
				broken_block.init_by_data(broken_block_data)
				break
	
	var all_broken_blocks_data: Array[BrokenBlockData] = []
	for broken_block in broken_blocks:
		all_broken_blocks_data.append(broken_block.data)
		
	level_data.broken_blocks_data = all_broken_blocks_data
	
	
func get_all_torch() -> Array[SceneTorch]:
	var result: Array[SceneTorch] = []
	for region in regions:
		result.append_array(region.get_torchs()) 
	return result
	

func init_torchs():
	var torchs_data = level_data.torchs_data
	var torchs = get_all_torch()
	
	for torch_data in torchs_data:
		for torch in torchs:
			if torch.data.id == torch_data.id:
				torch.init_by_data(torch_data)
				break
	
	var all_torchs_data: Array[TorchData] = []
	for torch in torchs:
		all_torchs_data.append(torch.data)
		
	level_data.torchs_data = all_torchs_data
	

func init_drop_items():
	for drop_item_data in level_data.drop_items_data:
		var drop_item_name = drop_item_data.item_config.drop_item_name
		var drop_pos = drop_item_data.drop_pos
		
		drop_item_no_fly(drop_item_name, drop_pos)


func find_summon_units_by_summon_item_name(summon_item_name: String) -> Array[UnitBase]:
	# name for example item_spell_summon_bat
	var result: Array[UnitBase] = []
	
	for index in range(units_parent.get_child_count()):
		var unit = units_parent.get_child(index) as UnitBase
		if unit.unit_data.summon_source_item and unit.unit_data.summon_source_item.name == summon_item_name:
			result.append(unit)
	
	return result
		
		
func world_2_screen_pos(world_pos: Vector2):
	var camera_left_top_pos = camera.global_position - get_viewport_rect().size / 2.0
	return world_pos - camera_left_top_pos
	

func screen_2_world_pos(screen_pos: Vector2):
	var camera_left_top_pos = get_camera_left_top_position_limit()
	return camera_left_top_pos + screen_pos
		
		
func set_crosshair_2_mouse_position():
	cross_hair.global_position = screen_2_world_pos(get_viewport().get_mouse_position())  
	

func get_camera_left_top_position_limit():
	var viewport_size = get_viewport_rect().size
	var camera_left_top_pos = camera.global_position - viewport_size / 2.0
	camera_left_top_pos.x = clamp(camera_left_top_pos.x, 0, camera.limit_right - viewport_size.x)
	camera_left_top_pos.y = clamp(camera_left_top_pos.y, 0, camera.limit_bottom - viewport_size.y)
	return camera_left_top_pos
	

func get_camera_center_position_limit():
	var viewport_size = get_viewport_rect().size
	var cam_left_top_position = get_camera_left_top_position_limit()
	return cam_left_top_position + viewport_size / 2.0 
	

func show_crosshair():
	cross_hair.visible = true


func hide_crosshair():
	cross_hair.visible = false
	
	
func _process(delta: float) -> void:
	process_move_crosshair()
	process_switch_map()
	process_scale_map()
	process_active_process_physicsprocess(delta)
			

func hide_game_pause_menu():
	ui_game_pause_menu.hide_pause_menu()
	show_crosshair()
	set_crosshair_2_mouse_position()
	Input.mouse_mode = Input.MOUSE_MODE_HIDDEN
	await get_tree().process_frame
	await get_tree().process_frame
	await get_tree().process_frame
	player.enable_input = true
	

func open_game_pause_menu():
	ui_game_pause_menu.show_pause_menu()
	hide_crosshair()
	player.enable_input = false
	Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	

func process_scale_map():
	if game_map_target_a < 1.0: return
	
	if Input.is_action_just_pressed("page_left"):
		game_map_target_scale -= game_map_scale_step
		if game_map_target_scale <= game_map_min_scale:
			game_map_target_scale = game_map_min_scale
			
	if Input.is_action_just_pressed("page_right"):
		game_map_target_scale += game_map_scale_step
		if game_map_target_scale >= game_map_max_scale:
			game_map_target_scale = game_map_max_scale
	
	game_map.map_display.scale = game_map.map_display.scale.lerp(Vector2(game_map_target_scale, game_map_target_scale), 0.08)
	
	
func process_switch_map():
	if Input.is_action_just_pressed("switch_map") and not ui_game_pause_menu.is_open(): 
		game_map_target_a = 0.0 if game_map_target_a == 1.0 else 1.0
		if game_map_target_a >= 1.0:
			game_map.map_display.scale = Vector2.ONE
			game_map_target_scale = 1.0
		
	game_map.modulate.a = lerp(game_map.modulate.a, game_map_target_a, 0.08)
	
	
func process_move_crosshair():
	if not cross_hair.visible: return
	
	set_crosshair_2_mouse_position()
	

func open_ui_card():
	destroy_ui_card()
	
	ui_card_view = UI_CARD.instantiate()
	canvas_layer.add_child(ui_card_view)
	ui_card_view.open()
	

func destroy_ui_card():
	if ui_card_view:
		ui_card_view.queue_free()
		ui_card_view = null
		

func init_crates():
	for crate_data in level_data.crates_data:
		var target = find_crate_by_id(crate_data.id) as WoodCrate
		if target:
			target.init_by_data(crate_data)
	
	level_data.crates_data = get_all_crates_data()
	

func is_toofar_from_player(node: Node2D):
	var distance = node.global_position.distance_to(camera.global_position)
	if distance > 250:
		return true
		
	return false	
	

func get_all_crates_data() -> Array[CrateData]:
	var result: Array[CrateData] = []
	
	for region in regions:
		result.append_array(region.get_all_crates_data()) 
		
	return result
	

func get_all_crates() -> Array[WoodCrate]:
	var result: Array[WoodCrate] = []
	
	for region in regions:
		result.append_array(region.get_all_crates()) 
		
	return result

	
func find_crate_by_id(id: String) -> WoodCrate:
	for region in regions:
		var result = region.find_crate_by_id(id)
		if result: return result
		
	return null
		

func process_active_process_physicsprocess(delta: float):
	update_process_active_timer -= delta
	if update_process_active_timer <= 0:
		update_process_active_timer = update_process_active_interval
		update_entity_active_process()
		

func disable_all_entity_process():
	for index in range(units_parent.get_child_count()):
		var unit = units_parent.get_child(index)
		disable_node(unit)
			
	for crate in all_crates:
		disable_node(crate)
			
	for door in all_doors:
		disable_node(door)
	
	for index in range(drop_items_parent.get_child_count()):
		var drop_item = drop_items_parent.get_child(index)
		disable_node(drop_item)
	
	for water in all_waters:
		disable_node(water)
	
	for damage_area in all_damage_areas:
		disable_node(damage_area)

	for dark_space in all_dark_spaces:
		disable_node(dark_space)
		
	for trap in all_traps:
		disable_node(trap)


func update_entity_active_process():
	if is_processing_active: return
	is_processing_active = true
	update_process_active_timer = update_process_active_interval
	
	var processed_count = 0
	for index in range(units_parent.get_child_count()):
		var unit = units_parent.get_child(index)
		update_node_active(unit)
			
		processed_count += 1
		if processed_count > 200:
			await get_tree().process_frame
			if not is_inside_tree():
				return
				
			processed_count = 0
	
	await get_tree().process_frame
	if not is_inside_tree():
		return
	processed_count = 0
	for crate in all_crates:
		if not is_inside_tree():
			return
		
		update_node_active(crate)
	
		processed_count += 1
		if processed_count > 200:
			await get_tree().process_frame
			if not is_inside_tree():
				return
			processed_count = 0
			
	await get_tree().process_frame
	if not is_inside_tree():
		return
	processed_count = 0
	for door in all_doors:
		update_node_active(door)
			
		processed_count += 1
		if processed_count > 200:
			await get_tree().process_frame
			if not is_inside_tree():
				return
			processed_count = 0
	
	await get_tree().process_frame
	if not is_inside_tree():
		return
	processed_count = 0
	for index in range(drop_items_parent.get_child_count()):
		var drop_item = drop_items_parent.get_child(index)
		update_node_active(drop_item)
		
		processed_count += 1
		if processed_count > 200:
			await get_tree().process_frame
			if not is_inside_tree():
				return
			processed_count = 0
	
	await get_tree().process_frame
	if not is_inside_tree():
		return
	for water in all_waters:
		update_node_active(water)
	
	await get_tree().process_frame
	if not is_inside_tree():
		return
	for damage_area in all_damage_areas:
		update_node_active(damage_area)

	await get_tree().process_frame
	if not is_inside_tree():
		return
	for dark_space in all_dark_spaces:
		update_node_active(dark_space)
		
	await get_tree().process_frame
	if not is_inside_tree():
		return
	for trap in all_traps:
		update_node_active(trap)
			
	is_processing_active = false
	

func update_node_active(node):
	if not is_instance_valid(node): return
	var toofar = is_toofar_from_player(node)
	if toofar:
		if node.has_method("disable_physics"):
			node.disable_physics()
		node.process_mode = Node.PROCESS_MODE_DISABLED
	else:
		if node.has_method("enable_physics"):
			node.enable_physics()
		node.process_mode = Node.PROCESS_MODE_INHERIT
		

func disable_node(node):
	if node.has_method("disable_physics"):
		node.disable_physics()
	node.process_mode = Node.PROCESS_MODE_DISABLED
	
	
func get_all_waters():
	var result: Array[Water] = []
	
	for region in regions:
		var waters = region.get_waters()
		result.append_array(waters)
	
	return result


func get_all_water_rects():
	var result: Array = []
	for water in all_waters:
		result.append(water.get_water_rect()) 
	return result
	

func get_all_damage_areas():
	var result: Array[DotDamageArea] = []
	
	for region in regions:
		var damage_areas = region.get_damage_areas()
		result.append_array(damage_areas)
	
	return result
	

func get_all_dark_spaces():
	var result: Array[DarkSpace] = []
	
	for region in regions:
		var dark_spaces = region.get_dark_spaces()
		result.append_array(dark_spaces)
	
	return result
	

func on_player_death():
	await ui_black_fade.fade_in().finished
	GameManager.delete_save_file()
	GlobalData.game_end_result = GlobalData.GameResult.Fail
	LevelManager.transition_game_end()


func on_game_finish():
	await ui_black_fade.fade_in().finished
	GameManager.delete_save_file()
	GlobalData.game_end_result = GlobalData.GameResult.Success
	LevelManager.transition_game_end()
	
	
func _exit_tree() -> void:
	AudioManager.stop_all_music()
	
	
	
	
	
